//=======================================================
/**
 * @description 小程序应用根级代理对象工厂
 * @author Brave Chan on 2019.5
 * @version 1.0.3
 */
//============================================================
import {
  isFunction,
  assignObjExcept,
  getSysId
} from '../utils/util';
import { execAry, execAsyncList } from '../utils/execList';
import {
  combineGlobalData,
  clearOriginData
} from './beconst';
import {
  APP_TYPE
} from '../constant/nodeType';
import {
  be_invokeOriginPares,
  be_mountMethods,
  be_invokeInitVMP,
  be_invokeParse,
  be_invokeDecorate,
  be_invokeOnShow,
  be_invokeOnHide
} from './BEModules';
import be_opt from '../constant/be_opt';
import Log from '../tools/BELog';
import { loadExecList, saveExecList } from './beGlobal';

//============================================================

/*
{
  data() {
    return {};
  },
  onLaunch(options) {},
  onShow(options) {},
  onHide() {},
  onError(msg) {},
  methods: {},
  methodAreas: []
}
*/



//============================================================
// parse 阶段
function originParse(origin, app) {
  let data = combineGlobalData(origin.data);

  // 调用需要parse阶段处理的模块
  be_invokeOriginPares(app, origin, APP_TYPE);

  app.globalData = data;
}

// 预初始化
function preInit(origin, app) {
  app._beID = getSysId();
  app.__initialized__ = false;
  app._beRoot = true;
  app._beNodeType = APP_TYPE;
}

// 绑定方法
function bindMethods(origin, app) {
  // methods
  app._methods = Object.assign({}, origin.methods || {});

  // methodAreas
  const areas = origin.methodAreas || [];
  let len = areas.length;

  let fns, entries;
  while (len--) {
    fns = areas[len];
    entries = Object.entries(fns);
    for (const [fnName, fn] of entries) {
      if (typeof fn === 'function') {
        app._methods[fnName] = fn;
      }
    }
  }
}

// 包装onLaunch
function packLaunch(origin, app) {
  app.onLaunch = (function (fn) {
    return function () {
      if (!this) {
        Log.error('In App onLaunch, can not get the this obj!!', this);
        if (be_opt.debug) {
          throw new Error('In App onLaunch, can not get the this obj!!');
        }
      } else {
        be_mountMethods(this);
        be_invokeInitVMP(this);
        be_invokeDecorate(this, this._be_vmp);
        be_invokeParse(this, this._be_vmp);
      }
      if (isFunction(fn)) {
        fn.apply(this, [...Array.from(arguments)]);
      }
      const execList = loadExecList(this._be_init_execs);
      // this._be_init_execs = null;
      // 执行预定函数队列
      if (Array.isArray(execList)) {
        execAry(this, execList);
      }
      // 执行含有异步的预定函数队列
      if (execList && execList.__isAsync) {
        execAsyncList(this, execList);
      }
    };
  })(origin.onLaunch);
}

// 包装onShow
function packShow(origin, app) {
  app.onShow = (function (fn) {
    return function () {
      be_invokeOnShow(this, this._be_vmp);

      if (isFunction(fn)) {
        fn.apply(this, [...Array.from(arguments)]);
      }
    };
  })(origin.onShow);
}

// 包装onError
function packError(origin, app) {
  app.onError = (function (fn) {
    return function (...args) {
      Log.error(args[0]);

      if (isFunction(fn)) {
        fn.apply(this, args);
      }
    };
  })(origin.onError);
}

// 包装onHide
function packHide(origin, app) {
  app.onHide = (function (fn) {
    return function (...args) {
      be_invokeOnHide(this, this._be_vmp);

      if (isFunction(fn)) {
        fn.apply(this, args);
      }
    };
  })(origin.onHide);
}
//============================================================
/**
 * @public
 * @description 根级代理工厂，也作为begonia的全局命名空间
 *
 * @param {object} app [required] 声明式开发对象
 * @returns {object} 适用于小程序App()的对象
 */
export default function BE(origin = {}, execList = []) {
  let result = [
    originParse,
    preInit,
    bindMethods,
    packLaunch,
    packShow,
    packError,
    packHide
  ].reduce(function (wApp, item) {
    item(origin, wApp);
    return wApp;
  }, {});

  if (execList && (Array.isArray(execList) || !!execList.__isAsync)) {
    result._be_init_execs = result._beID;
    saveExecList(result._beID, execList);
  }

  let app = assignObjExcept(
    result,
    origin,
    ['globalData', 'onLaunch', 'onShow', 'data', 'onError', 'onHide', 'methods', 'methodAreas']
  );

  clearOriginData(origin);

  origin = null;

  return app;
}
//============================================================
